﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Data.SqlClient;
using System.Linq;
using System.Text;

////////////////////////////////////////////////////////////////////////////////////////////////////
// namespace: Dimok
//
// summary:	.
////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Dimok {
	public static class LinqExtentions {

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	A DataContext extension method that gets a table type. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="TypeLoadException">	Thrown when typeload. </exception>
		///
		/// <param name="context">		The context to act on. </param>
		/// <param name="TableNames">	List of names of the tables. </param>
		///
		/// <returns>	The table type. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static Type GetTableType(this DataContext context, params string[] TableNames) {
			if (TableNames[0].Contains(".")) return Type.GetType(TableNames[0], true, true);
			var type = (from mt in context.Mapping.GetTables()
									join tn in TableNames.Select((tn, i) => new { Name = tn, Index = i })
									on mt.RowType.Type.Name.ToLower() equals tn.Name.ToLower()
									orderby tn.Index
									select mt.RowType.Type
								).FirstOrDefault();
			if (type == null)
				throw new TypeLoadException("Type " + context.GetType().GetBaseName() + "." + string.Join(",", TableNames) + " not found.");
			return type;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	A DataContext extension method that gets a table. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="context">		The context to act on. </param>
		/// <param name="TableName">	Name of the table. </param>
		///
		/// <returns>	The table. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static ITable GetTable(this DataContext context, string TableName) {
			var assembley = System.Reflection.Assembly.GetExecutingAssembly().FullName;
			return context.GetTable(TableName.Contains(".") 
				? Type.GetType(TableName + ", " + context.GetType().Assembly.FullName, true, true) 
				: context.GetTableType(TableName));
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	A Type extension method that gets a table name. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="TEntity">	The TEntity to act on. </param>
		///
		/// <returns>	The table name. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string GetTableName(this Type TEntity) {
			return TEntity.GetCustomAttributes(typeof(TableAttribute), true).Cast<TableAttribute>().Select(s => s.Name).SingleOrDefault() + "";
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	A Type extension method that gets a base table. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="TEntity">					The TEntity to act on. </param>
		/// <param name="connectionString">	The connection string. </param>
		///
		/// <returns>	The base table. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string GetBaseTable(this Type TEntity, string connectionString) {
			string viewName = ((TableAttribute)(TEntity.GetCustomAttributes(typeof(TableAttribute), true)[0])).Name;
			string schema = viewName.Split('.').Length > 1 ? viewName.Split('.')[0] : "dbo";
			string view = viewName.Split('.').Last();
			SqlDataAdapter da = new SqlDataAdapter(
@"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.VIEW_TABLE_USAGE
WHERE (VIEW_NAME = N'" + view + "') AND (VIEW_SCHEMA = N'" + schema + "') AND (TABLE_CATALOG = VIEW_CATALOG)", connectionString);
			DataTable dtSchema = new DataTable();
			da.Fill(dtSchema);
			var ret = dtSchema.AsEnumerable().
				Select(r => new { Name = r["TABLE_SCHEMA"] + "." + r["TABLE_NAME"], L = viewName.Levenshtein(r["TABLE_SCHEMA"] + "." + r["TABLE_NAME"]) }).
				OrderBy(a => a.L).Select(a => a.Name).
				FirstOrDefault();
			return ret;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable extension method that gets a data set. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="query">		The query to act on. </param>
		/// <param name="Context">	The context. </param>
		///
		/// <returns>	The data set. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataSet GetDataSet(this IQueryable query, DataContext Context){
			return GetDataSet(query, Context, new DataSet());
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable extension method that gets a data set. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="query">		The query to act on. </param>
		/// <param name="Context">	The context. </param>
		/// <param name="DataSet">	Set the data belongs to. </param>
		///
		/// <returns>	The data set. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataSet GetDataSet(this IQueryable query, DataContext Context, DataSet DataSet) {
			return GetDataSet(query, Context, DataSet, "", (Type)null);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable extension method that gets a data set. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="query">					The query to act on. </param>
		/// <param name="Context">				The context. </param>
		/// <param name="DataTableName">	Name of the data table. </param>
		///
		/// <returns>	The data set. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataSet GetDataSet(this IQueryable query, DataContext Context, string DataTableName) {
			return GetDataSet(query, Context, new DataSet(), DataTableName, (Type)null);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable extension method that gets a data set. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="query">			The query to act on. </param>
		/// <param name="Context">		The context. </param>
		/// <param name="EntityType">	Type of the entity. </param>
		///
		/// <returns>	The data set. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataSet GetDataSet(this IQueryable query, DataContext Context, Type EntityType) {
			return GetDataSet(query, Context, new DataSet(), "", EntityType);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable extension method that gets a data set. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="query">					The query to act on. </param>
		/// <param name="Context">				The context. </param>
		/// <param name="DataSet">				Set the data belongs to. </param>
		/// <param name="DataTableName">	Name of the data table. </param>
		/// <param name="EntityType">			Type of the entity. </param>
		///
		/// <returns>	The data set. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataSet GetDataSet(this IQueryable query, DataContext Context, DataSet DataSet, string DataTableName, Type EntityType) {
			if (DataSet == null) DataSet = new DataSet();
			DataSet.Tables.Add(GetDataTable(query, Context, DataTableName, EntityType));
			return DataSet;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable extension method that gets a data table. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="query">		The query to act on. </param>
		/// <param name="Context">	The context. </param>
		///
		/// <returns>	The data table. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataTable GetDataTable(this IQueryable query, DataContext Context) {
			return GetDataTable(query, Context, "", (Type)null);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable extension method that gets a data table. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="query">					The query to act on. </param>
		/// <param name="Context">				The context. </param>
		/// <param name="DataTableName">	Name of the data table. </param>
		/// <param name="EntityType">			Type of the entity. </param>
		///
		/// <returns>	The data table. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataTable GetDataTable(this IQueryable query, DataContext Context, string DataTableName, string EntityType) {
			return GetDataTable(query, Context, DataTableName, EntityType + "" == "" ? null : Context.GetTableType(EntityType));
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable extension method that gets a data table. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="query">					The query to act on. </param>
		/// <param name="Context">				The context. </param>
		/// <param name="dataTableName">	Name of the data table. </param>
		///
		/// <returns>	The data table. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataTable GetDataTable(this IQueryable query, DataContext Context, string dataTableName) {
			return GetDataTable(query, Context, dataTableName, null as Type);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable extension method that gets a data table. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="query">			The query to act on. </param>
		/// <param name="Context">		The context. </param>
		/// <param name="EntityType">	Type of the entity. </param>
		///
		/// <returns>	The data table. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataTable GetDataTable(this IQueryable query, DataContext Context, Type EntityType) {
			return GetDataTable(query, Context, "", EntityType);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable extension method that gets a data table. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="NotSupportedException">	Thrown when the requested operation is not supported. </exception>
		/// <exception cref="ArgumentException">			Thrown when one or more arguments have unsupported or
		/// 																					illegal values. </exception>
		///
		/// <param name="query">					The query to act on. </param>
		/// <param name="Context">				The context. </param>
		/// <param name="DataTableName">	Name of the data table. </param>
		/// <param name="EntityType">			Type of the entity. </param>
		///
		/// <returns>	The data table. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataTable GetDataTable(this IQueryable query, DataContext Context, string DataTableName, Type EntityType) {
			if (!Context.Connection.ConnectionString.ToLower().Contains("MultipleActiveResultSets".ToLower()))
				throw new NotSupportedException("MultipleActiveResultSets=True must be specified in connection string in order to use GetTable extention method.");
			if (DataTableName + "" == "") {
				var table = Context.Mapping.GetTable(query.ElementType);
				if (table == null && EntityType != null)
					table = Context.Mapping.GetTable(EntityType);
				if( table == null)
					throw new System.ArgumentException(
						"Table not found in "+Context.GetType().FullName,query.ElementType.FullName+(EntityType==null?"":","+EntityType.FullName));
				DataTableName = table.TableName;
			}
			DataTable dt = new DataTable(DataTableName);
			using (var sqlCommand = Context.GetCommand(query) as SqlCommand) {
				using (var da = new SqlDataAdapter(sqlCommand)) {
					da.Fill(dt);
				}
			}
			return dt;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable<TEntity> extension method that gets a data set. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <typeparam name="TEntity">	Type of the entity. </typeparam>
		/// <param name="query">		The query to act on. </param>
		/// <param name="Context">	The context. </param>
		///
		/// <returns>	The data set< t entity> </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataSet GetDataSet<TEntity>(this IQueryable<TEntity> query, DataContext Context) where TEntity : class {
			return GetDataSet(query, Context, new DataSet());
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable<TEntity> extension method that gets a data set. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <typeparam name="TEntity">	Type of the entity. </typeparam>
		/// <param name="query">		The query to act on. </param>
		/// <param name="Context">	The context. </param>
		/// <param name="DataSet">	Set the data belongs to. </param>
		///
		/// <returns>	The data set< t entity> </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataSet GetDataSet<TEntity>(this IQueryable<TEntity> query, DataContext Context, DataSet DataSet) where TEntity : class {
			return GetDataSet(query, Context, DataSet, "");
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable<TEntity> extension method that gets a data set. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <typeparam name="TEntity">	Type of the entity. </typeparam>
		/// <param name="query">					The query to act on. </param>
		/// <param name="Context">				The context. </param>
		/// <param name="DataTableName">	Name of the data table. </param>
		///
		/// <returns>	The data set< t entity> </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataSet GetDataSet<TEntity>(this IQueryable<TEntity> query, DataContext Context, string DataTableName) where TEntity : class {
			return GetDataSet(query, Context, new DataSet(), DataTableName);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable<TEntity> extension method that gets a data set. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <typeparam name="TEntity">	Type of the entity. </typeparam>
		/// <param name="query">					The query to act on. </param>
		/// <param name="Context">				The context. </param>
		/// <param name="DataSet">				Set the data belongs to. </param>
		/// <param name="DataTableName">	Name of the data table. </param>
		///
		/// <returns>	The data set< t entity> </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataSet GetDataSet<TEntity>(this IQueryable<TEntity> query, DataContext Context, DataSet DataSet, string DataTableName) where TEntity : class {
			if (DataSet == null) DataSet = new DataSet();
			DataSet.Tables.Add(GetDataTable(query, Context, DataTableName));
			return DataSet;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable<TEntity> extension method that gets a data table. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <typeparam name="TEntity">	Type of the entity. </typeparam>
		/// <param name="query">		The query to act on. </param>
		/// <param name="Context">	The context. </param>
		///
		/// <returns>	The data table< t entity> </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataTable GetDataTable<TEntity>(this IQueryable<TEntity> query, DataContext Context) where TEntity : class {
			return GetDataTable<TEntity>(query, Context, "");
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	An IQueryable<TEntity> extension method that gets a data table. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <typeparam name="TEntity">	Type of the entity. </typeparam>
		/// <param name="query">					The query to act on. </param>
		/// <param name="Context">				The context. </param>
		/// <param name="DataTableName">	Name of the data table. </param>
		///
		/// <returns>	The data table< t entity> </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static DataTable GetDataTable<TEntity>(this IQueryable<TEntity> query, DataContext Context, string DataTableName) where TEntity : class {
			if (DataTableName + "" == "") DataTableName = Context.Mapping.GetTable(query.ElementType).TableName;
			DataTable dt = new DataTable(DataTableName);
			new SqlDataAdapter(Context.GetCommand(query) as SqlCommand).Fill(dt);
			return dt;
		}
	}
}
